<?php
/**
 * XML-RPC server, embedded in a conventional dynamic page.
 *
 * URL: <this server's hostname>/<this page name>
 *
 * Security (eg, SSL, authentication) is handled by the web server
 * before the page is activated.
 *
 * All functions return an array with the results of the call.
 * 
 * The first element in the array is a boolean value that indicates
 * that the call was a success or failure.
 *
 * The second element contains the success or failure message as
 * a string.
 *
 * Any function-specific return data will follow as additional
 * elements in the array.
 *
 * @version $Id: xmlrpc.php,v 0.01 2007/02/20 12:00:00 jason Exp $
 * @author Benjamin Adams, Jason Noble and Matt Benjamin -- The Linuxbox Corp.
 *         {@link http://www.linuxbox.com}
 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
 * @package xmlrpc
 *//** */

require_once('../config.php');
require_once('../mod/quiz/locallib.php');


/***********************************************************************
											XML-RPC function definitions
***********************************************************************/

/* Method used to test the XMLRPC module */
function Ping($method_name, $params, $app_data){
	return "Success";
}


/* Method to create a new student account                                 */
/*                                                                        */
/* The input parameter may be an array containing new account information */
/* or alternately, account information can be sent individual parameters  */
/*                                                                        */
/* $params[0] - user name                                                 */
/* $params[1] - first name                                                */
/* $params[2] - last name                                                 */
/* $params[3] - email (optional)                                          */
function CreateAccount($method_name, $params, $app_data){
    /* check to see if input parameter 0 holds all account information */
    if (is_array($params[0])) {
        $new_user = $params[0];

        // make sure the user does not already exist
        if (!(get_record("user", "username", $new_user['username']))) {

            $retval = insert_record("user", $new_user);

            if (!$retval) // if insert failed, return fail
                return array(FALSE, "fail:insert record failed");
            else
                return array(TRUE, "success");

        } else { // user already exists; return fail
            return array(FALSE, "fail:user already exists");
        }
    }

    /* otherwise account information is being sent as individual parameters */

    $user_name = $params[0];
    $first_name = $params[1];
    $last_name = $params[2];
    if (count($params) > 3)
        $email_address = $params[3];
    else
        $email_address = "";
    // todo: check for more input parameters

    // make sure the user does not already exist
    if (!(get_record("user", "username", $user_name))) { 

        $new_user['username'] = $user_name;
        $new_user['firstname'] = $first_name;
        $new_user['lastname'] = $last_name;
        $new_user['email'] = $email_address;
        $new_user['maildisplay'] = 2;
        $new_user['mailformat'] = 1;
        $new_user['autosubscribe'] = 1;
        $new_user['htmleditor'] = 1;
        $new_user['city'] = "None";
        $new_user['country'] = "US";
        $new_user['lang'] = "en";
        $new_user['timezone'] = 99;
        $new_user['description'] = "";
        $new_user['url'] = "";
        $new_user['icq'] = "";
        $new_user['idnumber'] = "";
        $new_user['institution'] = "";
        $new_user['department'] = "";
        $new_user['phone1'] = "";
        $new_user['phone2'] = "";
        $new_user['address'] = "";
        $new_user['picture'] = "";
        $new_user['timemodified'] = time();
        $new_user['password'] = "";
        $new_user['confirmed'] = 1;
        $new_user['auth'] = "";

        $retval = insert_record("user", $new_user);

        if (!$retval)
            return array(FALSE, "fail:insert_record");
        else
            return array(TRUE, "success");
			
    } else { // user already exists; return fail	
        return array(FALSE, "fail:user already exists");
    }
}

/* Method to enable an existing student account that was previously disabled */
function EnableAccount($method_name, $params, $app_data){
    $user_name = $params[0];
	
    // make sure user exists in moodle database
    if (get_record("user", "username", $user_name)) {
        // set the deleted field to 0 for user (enabled)
        $retval = set_field("user", "deleted", 0, "username", $user_name);
        if ($retval) {
            return array(TRUE, "success");
        }
    }
	
    // return fail if user does not exist
    return array(FALSE, "fail:user does not exist");
}

/* Method to disable a student account */
function DisableAccount($method_name, $params, $app_data){
    $user_name = $params[0];

    // make sure user exists in moodle database
    if (get_record("user", "username", $user_name)) {
			// set the deleted field to 1 for the user (disabled)
			$retval = set_field("user", "deleted", 1, "username", $user_name);
			if ($retval) {
					return array(TRUE, "success");
			}
    }

    // return fail if user does not exist
    return array(FALSE, "fail:user does not exist");
}

// Method to disable a student account by ID
function DisableAccountByID($method_name, $params, $app_data){
  $userid = $params[0];

  // Modify the account
  $m_user = get_record("user", "id", $userid);
  $m_time = time();
  $m_user->deleted=1;
  $m_user->username=$m_user->email.".".$m_time;
  $m_user->email='';
  $m_user->timemodified=$m_time;

  if (update_record("user", $m_user)){
    delete_records('role_assignments', 'userid', $m_user->id); // unassign all roles
    return array(TRUE, "success");
  }else{
  // return fail if user does not exist
    return array(FALSE, "fail: could not update database");
  }
}

/* Method to  get and return account info by ID */
function GetAccountByID($method_name, $params, $app_data){
  $userid = $params[0];

  $m_user = false;
  $m_user = get_record("user", "username", $userid);

  // return result
	if($m_user){
	  return array(TRUE, "succcess", $m_user);
	}
	return array(FALSE, "Userid $userid not found in Moodle");
}

/* Method to update account information */
function UpdateAccount($method_name, $params, $app_data){
    $m_user = (object)$params[0];

    if (!update_record('user', $m_user)) {
        return array(FALSE, "Update failed!".print_r($params));
    }

    return array(TRUE, "success");
}

/* Method to enroll a student in a course */
function EnrollStudent($method_name, $params, $app_data){
    $user_name = $params[0];
    $course_name = $params[1];

    // make sure user exists in moodle database
    if (get_record("user", "username", $user_name)) {
        // get moodle user id for user
        $userid = get_field("user", "id", "username", $user_name);
		
        // make sure course exists
        if (get_record("course", "shortname", $course_name)) {
            // get course id for course
            $courseid = get_field("course", "id", "shortname", $course_name);
		
            // enroll the student in the course
            if (!enrol_student($userid, $courseid)) {
                return array(FALSE, "fail:enrol_student");
            }
        }
    }
	
    // return success if able to enroll student in the course
    return array(TRUE, "success");
}

function EnrollStudentByID($method_name, $params, $app_data){
    $user_id = $params[0];
    $course_id = $params[1];

    // enroll the student in the course
    if (!enrol_student($user_id, $course_id)) {
        return array(FALSE, "fail:enrol_student");
    }

    return array(TRUE, "success");
}

/* Method to unenroll a student from a course */
function UnenrollStudent($method_name, $params, $app_data){
    $user_name = $params[0];
    $course_name = $params[1];

    // make sure user exists in the moodle database
    if (get_record("user", "username", $user_name)) {
        // get moodle user id for user
        $userid = get_field("user", "id", "username", $user_name);

        // make sure course exists
        if (get_record("course", "shortname", $course_name)) {
            // get course id for course
            $courseid = get_field("course", "id", "shortname", $course_name);
			
            // make sure the student is enrolled in the course
            if (!get_record("user_students", "userid", $userid, "course", $courseid)) {
                return array(FALSE, "fail:get_record");
            }
			
            // unenroll the student from the course
            if (!unenrol_student($userid, $courseid)) {
                return array(FALSE, "fail:unenrol_student");
            }
        }
    }

    return array(TRUE, "success");
}

/* Method to unenroll a student from a course by ID */
function UnenrollStudentByID($method_name, $params, $app_data){
    $userid = $params[0];
    $courseid = $params[1];

    // unenroll the student from the course
    if (!unenrol_student($userid, $courseid)) {
        return array(FALSE, "fail:unenrol_student");
    }

    return array(TRUE, "successfully removed Userid: $userid from courseid: $courseid");
}


function AddTeacherByID($method_name, $params, $app_data){
    $user_id = $params[0];
    $course_id = $params[1];
    $role = $params[2];

    // enroll the teacher in the course
    if (!add_teacher($user_id, $course_id, $role)) {
        return array(FALSE, "fail:add_teacher");
    }

    return array(TRUE, "success");
}

function AddCreator($method_name, $params, $app_data){
    $user_id = $params[0];

    // add the creator
    if (!add_creator($user_id)) {
        return array(FALSE, "fail:add_creator");
    }

    return array(TRUE, "success");
}

function RemoveTeacherByID($method_name, $params, $app_data){
    $user_id = $params[0];
    $course_id = $params[1];
    $role = $params[2];

    // remove teacher from the course
    if (!remove_teacher($user_id, $course_id, $role)) {
        return array(FALSE, "fail:");
    }

    return array(TRUE, "success");
}


/* Method to add quiz responses for a student enrolled in a course (e.g. scantron results)  */
/* returns "success" or "fail".  Success also returns the quiz attempt unique id            */
/* Quiz types currently supported: Multiple Choice, True/False                              */
/*                                                                                          */
/* The input responses can either take the form of a single grade or be a string consisting */
/* the letters A to E corresponding to answer #1 to #5 or '?' for an unanswered question    */
/* For true/false - A corresponds to true, B to false                                       */
function InsertQuizResponses($method_name, $params, $app_data){
    global $CFG;
    $user_name = $params[0];
    $course_name = $params[1];
    $quiz_name = $params[2];
    $grade = $params[3];
    $responses = $params[4];

    // make sure that user, course, and quiz exist, and quiz is assigned to course
    if (($user = get_record("user", "username", $user_name)) && 
        ($course = get_record("course", "shortname", $course_name)) && 
        ($quiz = get_record("quiz", "name", $quiz_name, "course", $course->id))) {

        $timestamp = time();

        // if the quiz is already closed, pretend it is open for inserting responses
        if ($quiz->timeclose < $timestamp)
            $quiz->timeclose = $timestamp + 100000;

        $quiz_question_list = str_replace(',0', '', $quiz->questions);
        $quiz_questions = explode(",", $quiz_question_list);
        $num_questions = count($quiz_questions); // calculate number of quiz questions

        // Get number for the next attempt
    	if(!$attemptnumber = (int)get_field_sql('SELECT MAX(attempt)+1 FROM ' .
	                     "{$CFG->prefix}quiz_attempts WHERE quiz = '{$quiz->id}' AND " .
		             "userid = '{$user->id}'")) {
            $attemptnumber = 1;
        } 
		
        // Start a new attempt and initialize the question sessions		
        $timenow = time();
        $attempt->quiz = $quiz->id;
        $attempt->userid = $user->id;
        $attempt->preview = 0;
        $attempt->layout = $quiz->questions;  // don't shuffle questions
        $attempt->attempt = $attemptnumber;
        $attempt->sumgrades = 0.0;
        $attempt->timestart = $timenow;
        $attempt->timefinish = 0;
        $attempt->timemodified = $timenow;
        $attempt->uniqueid = question_new_attempt_uniqueid();

        // Save the attempt
        if (!$attempt->id = insert_record('quiz_attempts', $attempt)) {
            return array(FALSE, "fail:insert_record,quiz_attempts");
        }
									
        // if grade is not an empty string then ignore the responses 
        //   and insert grade as an integer
        if ($grade != "") {
            // reengineer raw score from percentage grade
            $raw_score = (integer)$grade * $num_questions / 100;  
			
            // update attempt
            $attempt->sumgrades = $raw_score;
            $attempt->timefinish = time();

            if (!$attempt->id = update_record('quiz_attempts', $attempt)) {
                return array(FALSE, "fail:update_record,quiz_attempts");
            }
		
            // update quiz_grade table with grade from latest attempt
            if ($quiz_grade = get_record('quiz_grades', 'quiz', $quiz->id, 'userid', $user->id)) {
                $quiz_grade->grade = $raw_score;
                $quiz_grade->timemodified = $timestamp;
				
                if (!update_record('quiz_grades', $quiz_grade))
                    return array(FALSE, "fail:update_record,quiz_grades");
            } else {
                // build quiz grade table input
                $quiz_grade->quiz = $quiz->id;
                $quiz_grade->userid = $user->id;
                $quiz_grade->grade = $raw_score;
                $quiz_grade->timemodified = $timestamp;
			
                if (!insert_record("quiz_grades", $quiz_grade))
                    return array(FALSE, "fail:insert_record,quiz_grades");
            }
			
        } else { // enter responses
			

            // get the answer for each quiz question
            // All responses will be an uppercase character ranging from A to E
            // Response string index 0 is the response for question 1, index 1
            //   is the response for question 2, etc.
            // For true false questions A is true and B is false
				
            $sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
                   "  FROM {$CFG->prefix}question q,".
                   "       {$CFG->prefix}quiz_question_instances i".
                   " WHERE i.quiz = '$quiz->id' AND q.id = i.question".
                   "   AND q.id IN ($quiz_question_list)";
			
            // Load the questions
            if (!$questions = get_records_sql($sql)) {
                return array(FALSE, "fail:get_records_sql");
            }
			
            // Load the question type specific information
            if (!get_question_options($questions)) { }
			
            // Create new sessions, creating states
            if (!$states = get_question_states($questions, $quiz, $attempt)) { }

            // Save all the newly created states
            foreach($questions as $i => $question) {
                save_question_session($questions[$i], $states[$i]);
            }

            $actions = array();
            $i = 0;
			
            // create submit action for each question
            foreach ($quiz_questions as $quiz_question) {
                $current_response = $responses{$i};
				
                // get current question
                $current_question = $questions[$quiz_question];
				
                // map response to correct question_answer id
                if ($current_question->qtype == "truefalse") {
                    $tf_answers = array_keys($current_question->options->answers);
                    if (strtoupper($current_response) == "A")
                        $answer = $current_question->options->trueanswer;
                    else if (strtoupper($current_response) == "B")
                        $answer = $current_question->options->falseanswer;
                    else if ($current_response == "?")
                        $answer = "";
                    else $answer = "";
                }
                else if ($current_question->qtype == "multichoice") {
                    $mc_answers = array_keys($current_question->options->answers);
                    switch (strtoupper($current_response)) {
                        case "A": $answer = $mc_answers[0]; break;
                        case "B": $answer = $mc_answers[1]; break;
                        case "C": $answer = $mc_answers[2]; break;
                        case "D": $answer = $mc_answers[3]; break;
                        case "E": $answer = $mc_answers[4]; break;
                        case "?": $answer = ""; break;
                        default : $answer = "";
                    }
                }
				
                $actions[$quiz_question]->responses[''] = $answer;
                $actions[$quiz_question]->timestamp = time();
			
                question_process_responses($questions[$quiz_question], $states[$quiz_question], $actions[$quiz_question], $quiz, $attempt);
                save_question_session($questions[$quiz_question], $states[$quiz_question]);
			
                $i++;
                if ($i > strlen($responses))
                    break;   // break out of loop if there are no more responses
            }
            $attempt->timemodified = $timestamp;
		
            // Set the attempt to be finished
            $attempt->timefinish = $timestamp;

            // Find all the questions for this attempt for which the newest
            // state is not also the newest graded state
            if ($closequestions = get_records_select('question_sessions', "attemptid = $attempt->uniqueid AND newest != newgraded", '', 'questionid, questionid')) {
                // load all the questions
                $closequestionlist = implode(',', array_keys($closequestions));
                $sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
                       "  FROM {$CFG->prefix}question q,".
                       "       {$CFG->prefix}quiz_question_instances i".
                       " WHERE i.quiz = '$quiz->id' AND q.id = i.question".
                       "   AND q.id IN ($closequestionlist)";
			
                if (!$closequestions = get_records_sql($sql)) {}

                // Load the question type specific information
                if (!get_question_options($closequestions)) {}

                // Restore the question sessions
                if (!$closestates = get_question_states($closequestions, $quiz, $attempt)) {}
			
                foreach($closequestions as $key => $question) {
                    $action->event = QUESTION_EVENTCLOSE;
                    $action->responses = $closestates[$key]->responses;
                    $action->timestamp = $closestates[$key]->timestamp;
                    question_process_responses($question, $closestates[$key], $action, $quiz, $attempt);
                    save_question_session($question, $closestates[$key]);
                }
            }
            $cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id); 
            add_to_log($course->id, 'quiz', 'close attempt', "review.php?attempt=$attempt->id", "$quiz->id", $cm->id);
			
            if (!update_record('quiz_attempts', $attempt)) {}
			
            quiz_save_best_grade($quiz, $user->id);
            $grd = $attempt->sumgrades;
            $grd = $quiz->sumgrades ? (($grd / $quiz->sumgrades) * $quiz->grade) : 0;
            $grd = intval(round($grd, 0));
			
        }
    }
    else {
        return array(FALSE, "fail:get_record,user-course-quiz");
    }
    
    return array(TRUE, "success", $attempt->uniqueid);
}


/* Method to return quiz feedback from incorrect responses to quiz questions  */
/*                                                                            */
/* Input parameters are user name, course name, quiz name, and quiz attempt   */
/*                                                                            */
/* Upon success returns the feedback for each question as individual elements */
/* in the return array                                                        */
function GetQuizFeedback($method_name, $params, $app_data){
	global $CFG;
    $user_name = $params[0];
    $course_name = $params[1];
    $quiz_name = $params[2];
    $quiz_attempt_id = $params[3];

    $feedback = array();
    if (($attempt = get_record("quiz_attempts", "uniqueid", $quiz_attempt_id)) &&
        ($quiz = get_record("quiz", "id", $attempt->quiz)) &&
        ($course = get_record("course", "id", $quiz->course))) {

        $qlist = quiz_questions_in_quiz($attempt->layout);
        $sql = "SELECT q.*, i.grade AS maxgrade, i.id AS instance".
               "  FROM {$CFG->prefix}question q,".
               "       {$CFG->prefix}quiz_question_instances i".
               " WHERE i.quiz = '$quiz->id' AND q.id = i.question".
               "   AND q.id IN ($qlist)";
        if (!$questions = get_records_sql($sql)) {}

        // Load the question type specific information
        if (!get_question_options($questions)) {}

        // Restore the question sessions to their most recent states
        if (!$states = get_question_states($questions, $quiz, $attempt)) {}

        if (!$lastattemptid = get_field('quiz_attempts', 'uniqueid', 'quiz', $attempt->quiz, 'userid', $attempt->userid, 'attempt', $attempt->attempt)) {}
        
        // for each question find the responses for the attempt
        $q_num = 0;
        foreach ($questions as $i => $question) {
            $statefields = 'n.questionid as question, s.*, n.sumpenalty';
            $sql = "SELECT $statefields".
                   "  FROM {$CFG->prefix}question_states s,".
                   "       {$CFG->prefix}question_sessions n".
                   " WHERE s.id = n.newgraded".
                   "   AND n.attemptid = '$lastattemptid'".
                   "   AND n.questionid = '$i'";
            if (!$laststate = get_record_sql($sql)) {}
            
            // Get the state so that we know what the responses were
            restore_question_state($questions[$i], $laststate);
           
            // Load the feedback for this question into the array
            $q_num++;
            if ($resp = $laststate->responses['']) {
                // override moodle feedback to 'correct' when correct answer is given
                if (get_field('question_answers', 'fraction', 'id', $resp) != 0)
                    $feedback[$q_num] = 'correct';
                // otherwise put the feedback from moodle into the array
                else
                    $feedback[$q_num] = get_field('question_answers', 'feedback', 'id', $resp);
            }
            // for invalid/blank answers, return the feedback 'not answered'
            else 
				$feedback[$q_num] = 'not answered'; 
        }
    }
    else {
        return array(FALSE, "fail:get_record,quiz_attempts-quiz-course");
    }
    
    /* return the feedback for the answers */
    return array(TRUE, "success", $feedback);
}

/* Create a new course */
function CreateCourse($method_name, $params, $app_data){
		$m_course = $params[0];
  	fix_course_sortorder();     //update course count in catagories
}


/* Delete a course completely given a course ID*/
function DeleteCourseByID($method_name, $params, $app_data){
  $course_id = $params[0];

  if (!delete_course($course_id)){	// delete the course
		return array(FALSE, "fail: DeleteCourseByID");
	}
  fix_course_sortorder();     //update course count in catagories

  return array(TRUE, "success");
}

/***********************************************************************
															Server setup
***********************************************************************/

// Create server
$server = xmlrpc_server_create();

// Register services
$services = array("AddTeacherByID","CreateAccount","CreateCourse","EnableAccount",
	"DeleteCourseByID","DisableAccount","DisableAccountByID","EnrollStudent",
	"EnrollStudentByID","GetAccountByID","GetQuizFeedback","RemoveTeacherByID",
	"InsertQuizResponses","UnenrollStudent","UnenrollStudentByID","UpdateAccount",
	"Ping");

foreach($services as $service){
	xmlrpc_server_register_method($server, $service, $service);
}

// handle requests
$request_xml = $HTTP_RAW_POST_DATA;

$app_data = $_SERVER['REMOTE_USER'];
$resp = xmlrpc_server_call_method($server,$request_xml,$app_data);
                               
print $resp;

xmlrpc_server_destroy($server);
?>
